package grpc

import (
	"fmt"
	"time"

	"gitee.com/zawei/yggdrasil/pkg/log"
	"gitee.com/zawei/yggdrasil/pkg/registry"
	"gitee.com/zawei/yggdrasil/pkg/transport"
	"google.golang.org/grpc"
	"google.golang.org/grpc/balancer/roundrobin"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/keepalive"
)

const (
	defaultSlowThreshold = time.Millisecond * 500
	defaultReadTimeout   = time.Second * 2
	defaultDialTimeout   = time.Second * 3
)

// Config ...
type Config struct {
	Name         string // config's name
	BalancerName string
	Target       string
	Block        bool
	DialTimeout  time.Duration
	ReadTimeout  time.Duration
	Direct       bool
	OnDialError  string // panic | error
	KeepAlive    *keepalive.ClientParameters
	Credentials  struct {
		Enable     bool
		CertFile   string
		ServerName string
	}
	AccessInterceptorLevel    string
	ShowLog                   bool
	SlowThreshold             time.Duration
	DisableAidInterceptor     bool
	DisableTimeoutInterceptor bool
	DisableAccessInterceptor  bool

	EnableTraceInterceptor  bool
	EnableMetricInterceptor bool

	registry    registry.Registry
	dialOptions []grpc.DialOption
}

// WithDialOption ...
func (config *Config) WithDialOption(opts ...grpc.DialOption) *Config {
	if config.dialOptions == nil {
		config.dialOptions = make([]grpc.DialOption, 0)
	}
	config.dialOptions = append(config.dialOptions, opts...)
	return config
}

// WithRegistry ...
func (config *Config) WithRegistry(registry registry.Registry) *Config {
	config.registry = registry
	return config
}

// Build ...
func (config *Config) Build() *grpc.ClientConn {

	if config.Target == "" {
		config.Target = fmt.Sprintf("%s:///%s.%s@%s", config.registry.Name(), transport.ServiceProvider, "grpc", config.Name)
	} else if config.registry != nil {
		config.Target = fmt.Sprintf("%s:///%s", config.registry.Name(), config.Target)
	}

	if config.ShowLog {
		setLogger()
	}
	if config.SlowThreshold == 0 {
		config.SlowThreshold = defaultSlowThreshold
	}

	if config.DialTimeout == 0 {
		config.DialTimeout = defaultDialTimeout
	}

	if config.ReadTimeout == 0 {
		config.ReadTimeout = defaultReadTimeout
	}

	if config.BalancerName == "" {
		config.BalancerName = roundrobin.Name
	}

	if !config.DisableAidInterceptor {
		config.dialOptions = append(config.dialOptions,
			grpc.WithChainUnaryInterceptor(aidUnaryClientInterceptor()),
		)
	}

	if !config.DisableTimeoutInterceptor {
		config.dialOptions = append(config.dialOptions,
			grpc.WithChainUnaryInterceptor(timeoutUnaryClientInterceptor(config.ReadTimeout, config.SlowThreshold)),
		)
	}

	if config.EnableTraceInterceptor {
		config.dialOptions = append(config.dialOptions,
			grpc.WithChainUnaryInterceptor(traceUnaryClientInterceptor()),
		)
	}

	if !config.DisableAccessInterceptor {
		if config.AccessInterceptorLevel == "" {
			config.AccessInterceptorLevel = "info"
		}
		config.dialOptions = append(config.dialOptions,
			grpc.WithChainUnaryInterceptor(loggerUnaryClientInterceptor(config.Name, config.AccessInterceptorLevel)),
		)
	}

	if config.EnableMetricInterceptor {
		config.dialOptions = append(config.dialOptions,
			grpc.WithChainUnaryInterceptor(metricUnaryClientInterceptor(config.Name)),
		)
		config.dialOptions = append(config.dialOptions,
			grpc.WithChainStreamInterceptor(metricStreamClientInterceptor(config.Name)),
		)
	}

	config.dialOptions = append(config.dialOptions,
		grpc.WithChainUnaryInterceptor(
			errorsUnaryClientInterceptor,
			mdUnaryClientInterceptor,
		),
	)
	config.dialOptions = append(config.dialOptions,
		grpc.WithChainStreamInterceptor(
			errorsStreamClientInterceptor,
			mdStreamClientInterceptor,
		),
	)
	if config.Credentials.Enable {
		creds, err := credentials.NewClientTLSFromFile(
			config.Credentials.CertFile, config.Credentials.ServerName,
		)
		if err != nil {
			log.Fatalw("fault to find credentials", "error", err)
		}
		config.dialOptions = append(config.dialOptions, grpc.WithTransportCredentials(creds))
	} else {
		config.dialOptions = append(config.dialOptions, grpc.WithInsecure())
	}

	return newGRPCClient(config)
}
